#include <glf/app.h>
#include <glf/core/types.h>
#include <glf/input/inputEventType.h>
#include <glf/core/mem.h>
#include <glf/core/tls.h>

#include <android/log.h>

#include "_jni_util.hpp"

#if GLF_ENABLE_DEBUGGER
#	include <glf/debugger/debugger.h>
#endif

#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, "GLF", __VA_ARGS__)

#include "../../projects/android/jni/jni_code.hpp"

namespace glf
{

void AndroidCreateView();
void AndroidSetViewSettings(int pixelSize, int depthSize, int stencilSize, int csaa, int useNonLinearDepth);

App::Impl* gAppImpl = 0;
glf::Globals* gGlobals = 0;
glf::App* gApp = 0;

static int gContextTls[Thread::FEATURE_MAX_THREADS];

struct App::Impl
	: ImplAppBase
{
	friend void AndroidSetNumExtraContext(int num);
	
	char mMachineName[64];
	int mStartExitHandlers;

	Impl(App& app, CreationFlag cf)
	   : ImplAppBase(app)
	   , mStartExitHandlers(-1)
	{
		gAppImpl = this;

		PropertyMap& pm = PropertyMap::Instance();
		pm.SetProperty("hw.platform",	"android");

		strcpy(mMachineName, "");
		//mApp.mFs.SetDataDir(rootPath);
		//mApp.mFs.SetHomeDir(homePath);
		//mApp.mFs.SetTempDir(tempPath);
	}

	bool Init(CreationSettings& cs)
	{
        mApp.mIsRunning = true;//c78 this line it is very important because the Run() function does not set it to true.
		mApp.mAppSettings.screenScale = 1;

		for(int i = 0; i < GLF_MAX_EXTRA_CONTEXT;) {
			int j = i + 1;
			mApp.mPlatformInfos.mExtraContext[i] = (void*)j;
			i = j;
		}

		mStartExitHandlers = Thread::AddStartExitHandlers(&AndroidOnStartThread, &AndroidOnExitThread, NULL);

		if (cs.supportedOrientations == ORIENTATION_UNDEFINED)
		{
			cs.supportedOrientations = ORIENTATION_LANDSCAPE_RIGHT | ORIENTATION_LANDSCAPE_LEFT;
		}

#if GLF_ENABLE_DEBUGGER
		if (cs.enableDebugger)
		{
			debugger::Debugger::GetInstance()->Init(cs.driverType);
		}
#endif
		
		return true;
	}

	bool Deinit()
	{
		Thread::RemoveStartExitHandlers(mStartExitHandlers);
		mStartExitHandlers = -1;
		/*if(gGlobals) {
			delete gGlobals;
			gGlobals = 0;
		}*/
		return true;
	}

	void GetWindowSize(int& width, int& height, int windowIndex) const
	{
		glf::MarkUsed(windowIndex);
		width = mApp.mAppSettings.screenWidth;
		height = mApp.mAppSettings.screenHeight;
	}
	
	void Update()
	{
#if GLF_ENABLE_DEBUGGER
		debugger::Debugger::GetInstance()->UpdateFrame();
#endif
	}
	
	void Run()
	{
	}

	void* GetMainContext() const {
		// trivially identified as mApp.mCreationSettings.extraContexts + 1
		return (void*)(mApp.mCreationSettings.extraContexts + 1);
	}

	void* GetCurrentContext() const {
		int id = gContextTls[Thread::GetSequentialThreadId()];
		if(id < 0) {
			return NULL;
		}
		// Note: we convert id on the fly on get because assigning 0 as the main
		// context in gContextTls is the safest way to initialize it for the
		// main thread without pondering when we should do this and possibly add
		// more java interaction stuff
		if(id == 0) {
			return GetMainContext();
		}
		return (void*)id;
	}

	bool SetCurrentContext(void* ctx) {
		Console::Println("trying to set context %d", (int)ctx);
		int id = (int)ctx;
		if(id == 0) {
			id = -1;
		} else if(id == (int)GetMainContext()) {
			id = 0;
		}
		if(AndroidSetCurrentContext(id)) {
			Console::Println("success setting context %d (actual: %d)", (int)ctx, id);
			gContextTls[Thread::GetSequentialThreadId()] = id;
			return true;
		}
		Console::Println("failed setting context %d (actual: %d)", (int)ctx, id);
		return false;
	}

	const char* GetDeviceName() const
	{
		return mMachineName;
	}

	void InitWindowSize(int width, int height)
	{
		mApp.mCreationSettings.windowWidth  = width;
		mApp.mCreationSettings.windowHeight = height;
	}

	void Resize(int width, int height)
	{
		mApp.mAppSettings.screenWidth	= width;
		mApp.mAppSettings.screenHeight	= height;
	}

private:

	void SetNumExtraContext(int num)
	{
		mApp.mCreationSettings.extraContexts = num;
	}
};

bool App::IsRunnable() const
{
	return false;
}

Globals* GetGlobals()
{
	return gGlobals;
}

// Called from Main Thread
void AndroidInit()
{
	LOGI("AndroidInit");
	Console::SetOutputDebugString(true);
	Console::Println("AndroidCreateView");
	AndroidCreateView();
}

// Called from GL Thread
void AndroidGetViewSettings()
{
	LOGI("AndroidGetViewSettings");
	if(!gApp)
	{
		gGlobals = new glf::Globals();
		const char* argv = "";
		Console::Println("NewApp");
		gApp = NewApp(0, const_cast<char**>(&argv));
		GLF_ASSERT(gApp);
		AndroidSetupPaths();
// 		gApp->GetFs().SetDataDir(gDataPath.c_str());
// 		gApp->GetFs().SetHomeDir(gHomePath.c_str());
// 		gApp->GetFs().SetTempDir(gTempPath.c_str());

		// clear context tls
		memset(gContextTls, -1, sizeof(gContextTls));

		if (gApp->MyInit())
		{
			Console::Println("MyInit succeeded");
			const CreationSettings& cs = gApp->GetCreationSettings();
			int csaa;
			switch (cs.antiAliasSetting)
			{
				case AA_NONE: csaa = 0; break;
				case AA_2X: csaa = 2; break;
				case AA_4X: csaa = 4; break;
				case AA_4XR: csaa = 4; break;
				default: csaa = 4;
			}			
			Console::Println("AndroidSetViewSettings(pixelSize: %d, zBufferSize: %d, stencilBufferSize: %d, CSAA: %d)",
							 cs.pixelSize, cs.zBufferSize, cs.useStencilbuffer ? 8 : 0, csaa);
			AndroidSetViewSettings(cs.pixelSize, cs.zBufferSize, cs.useStencilbuffer ? 8 : 0, csaa, cs.useNonLinearDepth ? 1 : 0);
		}
		else
		{
			Console::Println("MyInit failed");
		}
	}
}

// Called from Main Thread
void AndroidSetPaths(const char* resourcePath, const char* homePath, const char* tempPath)
{
	GLF_ASSERT(gApp);
	LOGI("resourcePath: %s", resourcePath);
	LOGI("homePath: %s", homePath);
	LOGI("tempPath: %s", tempPath);

	gApp->GetFs().SetDataDir(resourcePath);
	gApp->GetFs().SetHomeDir(homePath);
	gApp->GetFs().SetTempDir(tempPath);
}

// Called from GL Thread
void AndroidStateChanged(bool active)
{
	LOGI("AndroidStateChanged active=%d", active);
	
	if(gAppImpl)
	{
		GetAppEventMgr().SendEvent(SystemEvent(active ? SET_ENTERFOREGROUND : SET_ENTERBACKGROUND));
	}
}

// Called from GL Thread
void AndroidResizeScreen(int width, int height)
{
	
	static bool isSurfaceCreated = false;

	if (!isSurfaceCreated)
	{
		LOGI("Surface Created");
		if (gAppImpl)
		{
			LOGI("InitWindowSize %dx%d", width, height);
			gAppImpl->InitWindowSize(width, height);
		}
		isSurfaceCreated = true;

		gContextTls[Thread::GetSequentialThreadId()] = 0;
	}
	LOGI("AndroidResizeScreen %dx%d", width, height);
	if (gAppImpl) 
	{
		gAppImpl->Resize(width, height);
	}
}

// Called from GL Thread
void AndroidDestroy()
{
	LOGI("AndroidDestroy");

	if(gApp)
	{
		gApp->Destroy();
		gApp = 0;
	}
}

// Called from GL Thread
void AndroidUpdate()
{
	if(gApp)
	{
		gApp->Update();
	}
}

// Called from UI Thread
void AndroidAddTouchEvent(int flag, int x, int y, int id)
{
	if (glf::App::GetInstance() == 0) return;

	switch(flag)
	{
		case 0:
			GetAppInputMgr().GetGamepad(0).RaiseTouchEvent(0, glf::IET_TOUCHPAD_ENDED, id, x, y);
			break;

		case 1:
			GetAppInputMgr().GetGamepad(0).RaiseTouchEvent(0, glf::IET_TOUCHPAD_BEGAN, id, x, y);
			break;

		case 2:
			GetAppInputMgr().GetGamepad(0).RaiseTouchEvent(0, glf::IET_TOUCHPAD_MOVED, id, x, y);
			break;
	}
}

// Called from UI Thread
void AndroidOrientationChanged(int angle)
{
	static int sOldOrientationType = -1;
	//LOGI("AndroidOrientationChanged angle=%d", angle);
	
	OrientationEventType newOrientationType;
	if(angle >= 45 && angle < 135)
		newOrientationType  = OET_ASPECTLANDSCAPE_LEFT;
	else if(angle >= 135 && angle < 225)
		newOrientationType = OET_ASPECTPORTRAIT_REVERSED;
	else if(angle >= 225 && angle < 315)
		newOrientationType = OET_ASPECTLANDSCAPE_RIGHT;
	else
		newOrientationType = OET_ASPECTPORTRAIT;
	
	if(gAppImpl != NULL && newOrientationType != sOldOrientationType)
	{
		GetAppEventMgr().SendEvent(OrientationEvent(newOrientationType));
		sOldOrientationType = newOrientationType;
		LOGI("AndroidOrientationChanged %d", newOrientationType);
	}
}

// Called from context factory
void AndroidSetNumExtraContext(int num)
{
	gAppImpl->SetNumExtraContext(num);
}

void init() {
	if (gGlobals == NULL) 
	{
		gGlobals = new Globals();
	}
}

} //namespace glf
